#pragma once

#include"index.hpp"
#include"log.hpp"
#include<algorithm>
#include<vector>
#include<string>
#include<jsoncpp/json/json.h>

namespace ns_search
{
    // 用来解决文档重复的问题
    struct InvertedElemShow
    {
        std::vector<std::string> words;
        uint64_t doc_id;
        int weight;

        InvertedElemShow() : doc_id(0), weight(0) {}
    };

    class Searcher
    {
    public:
        Searcher() {}
        ~Searcher() {}
        
        // input就是经过parser处理的, 格式化的文档
        int cnt = 0;
        void InitSearch(const std::string& input)
        {
            // 1. 建立或获取index对象
            index = ns_index::Index::GetSingleton();
            LOG(Info, "建立单例index完成.....");

            // 2. 根据index对象构建索引
            index->BuildIndex(input);
            LOG(Info, "正排和倒排索引构建完成.....");

        }

        // query: 搜索关键字
        // json_string: 返回给用户浏览器的搜索结果
        void Search(const std::string& query, std::string* json_string)
        {
            // 1. 分词, 将query分词
            std::vector<std::string> query_results;
            ns_util::JiebaUtil::CutString(query, &query_results);

            // 2. 查找, 根据分词后的结果, 到index对象中查找
            // ns_index::InvertedList_t inverted_list_all;
            std::vector<InvertedElemShow> inverted_list_all;

            // 解决文档重复问题 <- 以doc_id作为标准
            std::unordered_map<uint64_t, InvertedElemShow> tokens_map;

            for(std::string word : query_results)
            {
                // 全部转小写, 索引的时候全部转小写了
                boost::to_lower(word);

                ns_index::InvertedList_t* inverted_list = index->GetInvertedList(word);
                if(nullptr == inverted_list)
                {
                    continue;
                }

                // 这里的不足之处就是: 同样一个文档因为搜索关键词的影响, 会被插入多遍, 导致文档重复问题
                // 比如: 你/是/一个/帅哥
                // inverted_list_all.insert(inverted_list_all.end(), inverted_list->begin(), inverted_list->end());
                for(auto& item : *inverted_list)
                {
                    auto& elem = tokens_map[item.doc_id];
                    elem.doc_id = item.doc_id;
                    elem.weight += item.weight;
                    elem.words.push_back(item.word);
                }
            }

            for(auto& item : tokens_map)
            {
                inverted_list_all.push_back(std::move(item.second));
            }

            // 3. 合并排序: 汇总查找结果, 按照相关性(weight)来降序排序
            // std::sort(inverted_list_all.begin(), inverted_list_all.end(), 
            // [](const ns_index::InvertedElem elem1, const ns_index::InvertedElem elem2)
            // {
            //     return elem1.weight > elem2.weight;
            // });
            std::sort(inverted_list_all.begin(), inverted_list_all.end(), 
            [](const InvertedElemShow& elem1, const InvertedElemShow& elem2)
            {
                return elem1.weight > elem2.weight;
            });

            // 4. 构建: 将查找的结果变成json串
            Json::Value root;
            for(auto& item : inverted_list_all)
            {
                ns_index::DocInfo* doc = index->GetForwardList(item.doc_id);
                if(nullptr == doc)
                {
                    continue;
                }

                Json::Value elem;
                elem["title"] = doc->title;
                // 这里由于一个文档涉及多个关键字, 但是肯定是有一个的, 那就words[0]吧
                elem["desc"] = GetDesc(doc->content, item.words[0]);
                elem["url"] = doc->url;

                // for debug
                elem["id"] = int(doc->doc_id);
                elem["weight"] = item.weight;

                root.append(elem);
            }

            Json::StyledWriter writer;
            *json_string = writer.write(root);
        }
    private:
        ns_index::Index* index; // 供系统进行查找的索引

        std::string GetDesc(const std::string& html_content, const std::string& word)
        {
            // 找到word在此文档中首次出现的位置(不可能没有出现, 因为是先根据word(关键词)倒排, 然后再正排找到摘要的)
            // 取它前50个字节(如果没有, 就从begin开始) 和 后100个字节(如果没有, 就从end结束) 构成摘要
            const std::size_t prev_step = 50;
            const std::size_t post_step = 100;

            std::size_t begin = 0,  end = html_content.size()-1;
            
            // std::size_t pos = html_content.find(word);
            auto iter = std::search(html_content.begin(), html_content.end(), word.begin(), word.end(), 
            [](int x, int y) { return std::tolower(x) == tolower(y);});
            
            // 这种情况是不可能存在的
            if(iter == html_content.end())
            {
                return "None1!";
            }
            std::size_t pos = std::distance(html_content.begin(), iter);

            // if(pos - prev_step > begin)  begin = pos - prev_step;
            // 没有考虑size_t类型
            if(pos > begin + prev_step)  begin = pos - prev_step;
            if(pos + post_step < end)  end = pos + post_step;

            if(begin >= end)
            {
                return "None2! ";
            }

            return html_content.substr(begin, end-begin);
        }
    };  
}
